package bubbleindex;

import Jama.Matrix;
import com.nativelibs4java.opencl.CLBuildException;
import com.nativelibs4java.opencl.CLContext;
import com.nativelibs4java.opencl.CLKernel;
import com.nativelibs4java.opencl.CLProgram;
import com.nativelibs4java.opencl.CLQueue;
import com.nativelibs4java.opencl.JavaCL;
import com.nativelibs4java.util.IOUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 *
 * @author ttrott
 * BuildIndex class contains the methods to manage the creation of The Bubble Index
 */
public class BuildIndex {
     
    volatile static boolean Stop;
    public static List<Float> ResultList;

    /**
     * Create goes through the bubble indices needed and calculates each one
     * @param DAILY_DATA_SIZE the number of price values in the daily price data file
     * @param NUMBER_OF_INDICES the number of bubble indices to create
     */
    public static void Create(int DAILY_DATA_SIZE, int NUMBER_OF_INDICES) {
        
        //First try to find GPU, if no GPU then catch and proceed with CPU
        try {
            CLContext context = JavaCL.createBestContext();
            CLQueue queue = context.createDefaultQueue();
            String src;
            
            BubbleIndex.displayOutput("GPU found. Using GPU.", false);
            
            try {
                src = IOUtils.readText(BuildIndex.class.getResource("TutorialKernels.cl"));
                CLProgram program = context.createProgram(src);
                CLKernel addFloatsKernel = program.createKernel("hq_derivative");
                
                for (int i = 0; i < NUMBER_OF_INDICES; i++){			
                    
                    Stop = false;
                    ResultList = new ArrayList<>();
                    List<String> DateList = new ArrayList<>();
                    List<String> DataList = new ArrayList<>();
            
                    BubbleIndex.PreviousFilePath = Indices.userDir + "ProgramData" + 
                            Indices.filePathSymbol + BubbleIndex.CategoryName + Indices.filePathSymbol + 
                            BubbleIndex.SelectionName + Indices.filePathSymbol + BubbleIndex.SelectionName + 
                            Integer.toString(BubbleIndex.BacktestDayLengths[i]) + "days.csv";
            
                    if (DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i] <= 1) {
                        Stop = true;
                    }

                    int START_INDEX;

                    if (InputData.checkForFile(BubbleIndex.PreviousFilePath)) {

                        InputData.ReadValues(BubbleIndex.PreviousFilePath, DataList, 
                            DateList, true, true);
				
                        int UpdateLength = BubbleIndex.DailyPriceDateList.size() 
                                - updateDateMatch(DateList) - 1;

                        if (Stop) {
                            System.out.println("Dates in the previous file: " + 
                                    BubbleIndex.PreviousFilePath + " do not match. Code 028.");
                            BubbleIndex.displayOutput("Dates in the previous file: " + 
                                    BubbleIndex.PreviousFilePath + " do not match.", false);
                            break;
                        }
                        
                        START_INDEX = DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i] 
                                - UpdateLength;   
                    }
			
                    else {
                        START_INDEX = 0;         
                    }
                
                    ExecutorService executor = Executors.newFixedThreadPool(BubbleIndex.ThreadNumber);

                    List<Callable<Float>> callables = new ArrayList<>();

                    createCallables(context, addFloatsKernel, queue, callables, START_INDEX, 
                        DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i], i);

                    List<Future<Float>> results = executor.invokeAll(callables);
                    
                    for (Future<Float> futures : results) {
                        ResultList.add(futures.get());
		    }
                        
                    List copy = new ArrayList(ResultList);
                    
                    executor.shutdownNow();

                    if (!Stop) {
                        String Name = BubbleIndex.SelectionName + 
                            BubbleIndex.BacktestDayLengths[i] + "days.csv";

                        ExportData.WriteCSV(copy, DAILY_DATA_SIZE - BubbleIndex.BacktestDayLengths[i], 
                            Name, InputData.checkForFile(BubbleIndex.PreviousFilePath));
                    }
                }
        
                program.release();
                addFloatsKernel.release();
        
            } catch (IOException | InterruptedException | ExecutionException ex) {
                ex.printStackTrace();
                System.out.println("Exception. Code 011.");
            }
        
            queue.flush();
            queue.release();
            context.release();
            
        } catch (CLBuildException th) {
            System.out.println("CLBuildException. Code 012.");            
            BubbleIndex.displayOutput("No GPU found. Using CPU.", false);     
            
            //If no GPU, then run with CPU only
            noGPU.Create(DAILY_DATA_SIZE, NUMBER_OF_INDICES);
        }
    }
    
    /**
     * 
     * @param context the GPU context
     * @param addFloatsKernel the GPU kernel
     * @param queue the GPU kernel queue
     * @param callables the list of callables
     * @param START the start index
     * @param SIZE the number of values to calculate
     * @param INDEX the index corresponding to the backtest day length array
     */
    private static void createCallables(CLContext context, CLKernel addFloatsKernel, 
            CLQueue queue, List<Callable<Float>> callables, int START, int SIZE, int INDEX) {

        for (int j = START; j < SIZE; j++) {
            callables.add(new MyCallable(context, addFloatsKernel, queue, j, 
                    BubbleIndex.BacktestDayLengths[INDEX]));   
        }                   
    }

    /**
     * updateDateMatch searches the previously created file and compares the
     * last calculated date with the dates in the daily price data
     * @param DateList the list of dates from previous file
     * @return the index value of the Daily Price Data List where date values match
     */
    public static int updateDateMatch(List<String> DateList) {
        final String finalValue = DateList.get(DateList.size() - 1);

        for (int i = 0; i < BubbleIndex.DailyPriceDateList.size(); i++) {
            if (finalValue.equals(BubbleIndex.DailyPriceDateList.get(i))) {    
                return i;
            }
        }
        
        /*Since previous file exists but no dates matched, must be error... 
        start from beginning*/
        Stop = true;
        return 0;                        
    }
    
     /**
     * DataReverse method takes an array and reverses the linear order of the
     * array.
     * @param Data The array to be reversed
     * @param SIZE The size of the array
     */   
    public static void DataReverse(double[] Data, final int SIZE) {
        
        double[] Temp = new double[SIZE];
        for (int i = 0; i < SIZE; i++) {
            Temp[i] = Data[SIZE - 1 - i];
        }
        
        System.arraycopy(Temp, 0, Data, 0, SIZE);      
    }  

    /**
     * 
     * @param SelectedData
     * @param NumberOfDays 
     */
    public static void Normalize(double[] SelectedData, int NumberOfDays) {
		
		//Delcare and initialize array of returns
        double[] ReturnsData = new double[NumberOfDays];
        ReturnsData[0] = 0.0;
        for (int i = 1; i < NumberOfDays; i++) {
            ReturnsData[i] = (SelectedData[i] - SelectedData[i-1]) * 1.0 / 
                    SelectedData[i-1];
        }
                //Declare and initialize array of normalized price values
        SelectedData[0] = 100.0;

        for (int i = 1; i < NumberOfDays; i++) {
            SelectedData[i] = SelectedData[i-1] * ReturnsData[i] + SelectedData[i-1];
        }

        //Finally, need to take the log of the normalized price values
        for (int i = 0; i < NumberOfDays; i++) {
                SelectedData[i] = Math.log(SelectedData[i]);
        }
    }

    /**
     * The LinearFit method solves the b = Ax matrix for the x vector. In other
     * words this is a linear regression to fit the equation to the log prices.
	 * The equation is y = b + Ax1 + Bx2 where x1 and x2 are TimeValues_M_power
	 * and LogCosTimeValues respectively.
     * @param Data The array containing the log prices
     * @param TimeValues_M_Power Array of time values raised to the M power
     * @param LogCosTimeValues Array of logcos time values
     * @param Coef Array containing the models fitted coefficients
     * @param SIZE Size of the data window (days)
     */
    public static void LinearFit(double[] Data, double[] TimeValues_M_Power,
            double[] LogCosTimeValues, double[] Coef, final int SIZE){
            
    	double[][] Array = new double[SIZE][3];
            
    	for (int i = 0; i < SIZE; i++) {
    	    Array[i][0] = 1.0;
    	    Array[i][1] = TimeValues_M_Power[i];
    	    Array[i][2] = LogCosTimeValues[i];
        }
    
        Matrix A = new Matrix(Array);
        Matrix b = new Matrix(Data, SIZE);
        Matrix x = A.solve(b);
            
        for (int i = 0; i < 3; i++) {
    	    Coef[i] = x.get(i,0);
        }
    }              
}